package info.batcloud.fanli.core.service.impl;

import com.ctospace.archit.common.pagination.Paging;
import info.batcloud.fanli.core.dto.RedPacketDTO;
import info.batcloud.fanli.core.entity.RedPacket;
import info.batcloud.fanli.core.entity.User;
import info.batcloud.fanli.core.enums.RedPacketStatus;
import info.batcloud.fanli.core.repository.CrowdRepository;
import info.batcloud.fanli.core.repository.RedPacketRepository;
import info.batcloud.fanli.core.repository.UserRedPacketRepository;
import info.batcloud.fanli.core.repository.UserRepository;
import info.batcloud.fanli.core.service.CrowdService;
import info.batcloud.fanli.core.service.RedPacketService;
import info.batcloud.fanli.core.service.UserRedPacketService;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class RedPacketServiceImpl implements RedPacketService {

    @Inject
    private RedPacketRepository redPacketRepository;

    @Inject
    private UserRedPacketRepository userRedPacketRepository;

    @Inject
    private UserRepository userRepository;

    @Inject
    private UserRedPacketService userRedPacketService;

    @Inject
    private CrowdService crowdService;

    @Inject
    private CrowdRepository crowdRepository;

    @Override
    public long createRedPacket(RedPacketCreateParam param) {
        RedPacket packet = new RedPacket();
        BeanUtils.copyProperties(param, packet);
        packet.setCreateTime(new Date());
        packet.setCrowd(crowdRepository.findOne(param.getCrowdId()));
        redPacketRepository.save(packet);
        return packet.getId();
    }

    @Override
    public void updateRedPacket(long id, RedPacketUpdateParam param) {
        RedPacket packet = redPacketRepository.findOne(id);
        BeanUtils.copyProperties(param, packet);
        packet.setUpdateTime(new Date());
        packet.setCrowd(crowdRepository.findOne(param.getCrowdId()));
        redPacketRepository.save(packet);
    }

    @Override
    public void setStatus(long id, RedPacketStatus status) {
        RedPacket packet = redPacketRepository.findOne(id);
        packet.setStatus(status);
        redPacketRepository.save(packet);
    }

    @Override
    @Transactional
    public synchronized int grantToUser(long userId) {
        Date now = new Date();
        List<RedPacket> redPackets
                = redPacketRepository.findByStartTimeLessThanEqualAndEndTimeGreaterThanEqualAndStatus(now, now, RedPacketStatus.VALID);
        User user = userRepository.findOne(userId);
        int num = 0;
        for (RedPacket redPacket : redPackets) {
            if (redPacket.getTotalNum() <= redPacket.getGrantNum()) {
                continue;
            }
            boolean flag = crowdService.checkUser(user, redPacket.getCrowd());
            if (!flag) {
                continue;
            }
            flag = checkFreq(userId, redPacket);
            if (!flag) {
                continue;
            }
            UserRedPacketService.UserRedPacketAddParam addParam = new UserRedPacketService.UserRedPacketAddParam();
            addParam.setRedPacketId(redPacket.getId());
            addParam.setUserId(userId);
            userRedPacketService.addUserRedPacket(addParam);
            redPacket.setGrantNum(redPacket.getGrantNum() + 1);
            num++;
        }
        redPacketRepository.save(redPackets);
        return num;
    }

    private boolean checkFreq(long userId, RedPacket redPacket) {
        switch (redPacket.getFreq()) {
            case TIMES:
                return userRedPacketRepository.countByUserIdAndRedPacketId(userId, redPacket.getId()) < redPacket.getMaxUserGrantNum();
            case MONTH:
                Date today = new Date();
                Date monthFirstDay = DateUtils.truncate(today, Calendar.MONTH);
                return userRedPacketRepository.countByUserIdAndRedPacketIdAndCreateTimeBetween(userId, redPacket.getId(), monthFirstDay, today)
                        < redPacket.getMaxUserGrantNum();
            case DAY:
                Date now = new Date();
                return userRedPacketRepository.countByUserIdAndRedPacketIdAndCreateTimeBetween(userId, redPacket.getId(), DateUtils.truncate(now, Calendar.DATE), now)
                        < redPacket.getMaxUserGrantNum();
        }
        return false;
    }

    @Override
    public Paging<RedPacketDTO> search(SearchParam param) {
        Specification<RedPacket> specification = (root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            if (query.getResultType() != Long.class) {
                root.fetch("crowd", JoinType.LEFT);
            }
            List<Expression<Boolean>> expressions = predicate.getExpressions();
            if (param.getStatus() != null) {
                expressions.add(cb.equal(root.get("status"), param.getStatus()));
            } else {
                expressions.add(cb.notEqual(root.get("status"), RedPacketStatus.DELETED));
            }
            if (param.getType() != null) {
                expressions.add(cb.equal(root.get("type"), param.getType()));
            }
            return predicate;
        };
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = new PageRequest(param.getPage() - 1,
                param.getPageSize(), sort);
        Page<RedPacket> page = redPacketRepository.findAll(specification, pageable);
        List<RedPacketDTO> dtoList = page.getContent().stream()
                .map(o -> toBO(o)).collect(Collectors.toList());
        return Paging.of(dtoList, Long.valueOf(page.getTotalElements()).intValue(),
                param.getPage(), param.getPageSize());
    }

    private RedPacketDTO toBO(RedPacket redPacket) {
        RedPacketDTO bo = new RedPacketDTO();
        BeanUtils.copyProperties(redPacket, bo);
        bo.setCrowdId(redPacket.getCrowd().getId());
        bo.setCrowdName(redPacket.getCrowd().getName());
        return bo;
    }

}
